package stardust.core.java.reflect.scaner;


import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import stardust.Stardust;


public class ResourceScaner
{

  public static final String URL_DECODE = "UTF-8";

  public static final String PROTOCOL_FILE = "file";

  public static final String PROTOCOL_JAR = "jar";

  public static final String PROTOCOL_ZIP = "zip";

  private ScanFilter filter;

  public ResourceScaner(ScanFilter filter)
  {
    this.filter = filter;
  }

  public List<Resource> scan(String basePackage) throws IOException
  {
    List<Resource> result = new ArrayList<Resource>();
    Enumeration<URL> resources;
    URL u;
    File file;

    basePackage = basePackage.replace('.', '/');
    basePackage = basePackage.startsWith("/") ? basePackage.substring(1) : basePackage;
    resources = Thread.currentThread().getContextClassLoader().getResources(basePackage);

    while (resources.hasMoreElements())
    {
      u = resources.nextElement();

      if (u.getProtocol().equals(PROTOCOL_FILE))
      {
        String basePath, scanString;
        file = new File(URLDecoder.decode(u.getFile(), URL_DECODE));

        basePath = file.getAbsolutePath().replace('\\', '/');
        basePath = basePath.endsWith("/") ? basePath.substring(0, basePath.length() - 1) : basePath;

        scanString = basePackage;
        scanString = scanString.startsWith("/") ? scanString.substring(1) : scanString;
        scanString = scanString.endsWith("/") ? scanString.substring(0, scanString.length() - 1) : scanString;

        basePath = basePath.substring(0, basePath.length() - scanString.length());

        this.scanFileSystem(file, result, basePath, scanString);
      }
      else if (u.getProtocol().equals(PROTOCOL_JAR) && this.isJar(u.getFile()))
      {
        try
        {
          this.scanJar(new JarFile(this.getJarFile(u.getFile())), result, this.getJarFile(u.getFile()));
        }
        catch (Throwable e)
        {
          ;
        }
      }
      else if (u.getProtocol().equals(PROTOCOL_ZIP) && this.isJar(u.getFile()))
      {
        try
        {
          this.scanZip(new ZipFile(this.getZipFile(u.getFile())), result, this.getZipFile(u.getFile()));
        }
        catch (Throwable e)
        {
          ;
        }
      }
      else
      {
        System.err.println("警告：无法处理的扫描项目[" + u + "]。");
      }
    }

    return result;
  }

  protected void scanFileSystem(File dir, List<Resource> result, String basePath, String scanString)
  {
    String canonicalPath;
    Resource resource;

    if (dir.isDirectory())
    {
      for (int i = 0; i < dir.listFiles().length; i++)
      {
        this.scanFileSystem(dir.listFiles()[i], result, basePath, scanString);
      }
    }
    else
    {
      if (this.filter != null)
      {
        canonicalPath = dir.getAbsolutePath().substring(basePath.length()).replace('\\', '/');
        canonicalPath = canonicalPath.startsWith("/") ? canonicalPath.substring(1) : canonicalPath;
        canonicalPath = canonicalPath.endsWith("/") ? canonicalPath.substring(0, canonicalPath.length() - 1) : canonicalPath;
        resource = new Resource(basePath, canonicalPath, null);

        if (this.filter.filter(resource))
        {
          resource.setInputStream(Stardust.loadResourceAsInputStream(dir.getAbsolutePath()));
          result.add(resource);
        }
      }
    }
  }

  protected void scanJar(JarFile jar, List<Resource> result, String basePath)
  {
    Enumeration<JarEntry> files = jar.entries();
    JarEntry e;
    Resource resource;

    while (files.hasMoreElements())
    {
      e = files.nextElement();

      if (!e.isDirectory())
      {
        resource = new Resource(basePath, e.getName().startsWith("/") ? e.getName().substring(1) : e.getName(), null);

        if (this.filter != null && this.filter.filter(resource))
        {
          try
          {
            resource.setInputStream(jar.getInputStream(e));
            result.add(resource);
          }
          catch (IOException ex)
          {
          }
        }
      }
    }
  }

  protected void scanZip(ZipFile zip, List<Resource> result, String basePath)
  {
    Enumeration<? extends ZipEntry> files = zip.entries();
    ZipEntry e;
    Resource resource;

    while (files.hasMoreElements())
    {
      e = files.nextElement();

      if (!e.isDirectory())
      {
        resource = new Resource(basePath, e.getName().startsWith("/") ? e.getName().substring(1) : e.getName(), null);

        if (this.filter != null && this.filter.filter(resource))
        {
          try
          {
            resource.setInputStream(zip.getInputStream(e));
            result.add(resource);
          }
          catch (IOException ex)
          {
          }
        }
      }
    }
  }

  private boolean isJar(String file)
  {
    return file.indexOf("!/") != -1 || file.toLowerCase().endsWith("." + PROTOCOL_JAR) ? true : false;
  }

  private String getJarFile(String file) throws UnsupportedEncodingException
  {
    int offset = file.indexOf("!/");

    file = URLDecoder.decode(file.substring(0, offset), URL_DECODE);
    // file = file.substring(file.indexOf(':') + 1);
    // file = file.startsWith("/") ? file.substring(1) : file;

    return file;
  }

  private boolean isZip(String file)
  {
    return file.indexOf("!/") != -1 || file.toLowerCase().endsWith("." + PROTOCOL_ZIP) ? true : false;
  }

  private String getZipFile(String file) throws UnsupportedEncodingException
  {
    int offset = file.indexOf("!/");

    file = URLDecoder.decode(file.substring(0, offset), URL_DECODE);
    // file = file.substring(file.indexOf(':') + 1);
    // file = file.startsWith("/") ? file.substring(1) : file;

    return file;
  }

  public static void main(String[] args) throws IOException
  {
    ResourceScaner cs = new ResourceScaner(new ScanFilter()
    {
      public boolean filter(Resource resource)
      {
        System.out.println(resource.getBasePath() + " -> " + resource.getPath());
        return false;
      }
    });

    cs.scan("org");
  }

}
